package dict

import (
	"reflect"
	"strings"
)

// StructToDict 将结构体转换为字典
// data 结构体数据
// tag 结构体字段的tag名称，如果为空则使用结构体字段的名称
func StructToDict(data any, tag string) *Dict {
	// 如果数据为空，则直接返回空字典
	if data == nil {
		return NewDict()
	}

	// 如果数据是map类型
	if reflect.TypeOf(data).Kind() == reflect.Map {
		return mapToDict(data)
	}

	// 如果数据是结构体类型
	if reflect.TypeOf(data).Kind() == reflect.Struct {
		return structToDict(data, tag)
	}

	// 如果数据不是结构体类型，则直接返回空字典
	if reflect.ValueOf(data).Elem().Kind() != reflect.Struct {
		return NewDict()
	}

	// 如果数据是结构体指针类型
	return ptrToDict(data, tag)
}

// ptrToDict 将结构体指针转换为字典
func ptrToDict(data any, tag string) *Dict {
	dataType := reflect.TypeOf(data).Elem()
	dataValue := reflect.ValueOf(data).Elem()

	return build(dataType, dataValue, tag)
}

// structToDict 将结构体转换为字典
func structToDict(data any, tag string) *Dict {
	// 如果数据是结构体类型
	dataType := reflect.TypeOf(data)
	dataValue := reflect.ValueOf(data)

	return build(dataType, dataValue, tag)
}

func build(dataType reflect.Type, dataValue reflect.Value, tag string) *Dict {
	dict := NewDict()
	for i := 0; i < dataType.NumField(); i++ {
		field := dataType.Field(i)
		value := dataValue.FieldByName(field.Name)

		// 过滤非公开字段
		if !isPublicField(field.Name) {
			continue
		}

		// 处理tag
		name := field.Name
		if tag != "" {
			tagValue := field.Tag.Get(tag)
			// 如果tag中包含"-"，则表示要忽略该字段
			if tagValue == "-" {
				continue
			}
			if len(tagValue) > 0 {
				// 如果tag中包含","，只取第一个名称作为字典的key
				vals := strings.Split(tagValue, ",")
				name = vals[0]
			}

			dict.Add(name, value.Interface())
		}
	}
	return dict
}

// mapToDict 将map转换为字典
func mapToDict(data any) *Dict {
	dict := NewDict()
	for key, value := range data.(map[string]any) {
		dict.Add(key, value)
	}
	return dict
}

// isPublicField 判断字段是否是公开字段
func isPublicField(name string) bool {
	if len(name) > 0 && isLetterUpper(name[0]) {
		return true
	}
	return false
}

// isLetterUpper 判断字符是否是大写字母
func isLetterUpper(b byte) bool {
	if b >= byte('A') && b <= byte('Z') {
		return true
	}
	return false
}
